﻿// =====================================================================
//
//  This file is part of the Microsoft Dynamics CRM SDK code samples.
//
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//  This source code is intended only as a supplement to Microsoft
//  Development Tools and/or on-line documentation.  See these other
//  materials for detailed information regarding Microsoft code samples.
//
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
// =====================================================================

using System;
using System.ServiceModel;
using System.Xml.Linq;

#if SILVERLIGHT
using System.Security.Cryptography;
#else
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;
#endif

namespace SL.IdentityModel.Protocols.WSTrust
{
	/// <summary>
	/// This class contains infromation about the WS-Trust RequestSecurityTokenResponse
	/// </summary>
	public class RequestSecurityTokenResponse
	{
		private RequestedSecurityToken _requestedSecurityToken;
		private EndpointAddress _appliesTo = null;
		private string _computedKeyAlgorithm = null;
		private DateTime _created;
		private byte[] _entropyBytes = null;
		private DateTime _expires;
		private int? _keySizeInBits = null;
		private string _keyType = null;
		private XNode _proofTokenReference = null;
		private byte[] _protectedKeyBytes = null;
		private RequestSecurityToken _rst;
		private byte[] _symmetricProofTokenBytes = null;
		private bool _symmetricProofTokenComputed = false;
		private object _symmetricProofTokenSyncRoot = new object();

		/// <summary>
		/// the requested security token
		/// </summary>
		public RequestedSecurityToken RequestedSecurityToken
		{
			get { return _requestedSecurityToken; }
			set { _requestedSecurityToken = value; }
		}

		/// <summary>
		/// AppliesTo infomation from the response
		/// </summary>
		public EndpointAddress AppliesTo
		{
			get { return _appliesTo; }
			set { _appliesTo = value; }
		}

		/// <summary>
		/// Combined entropy for client/ server
		/// </summary>
		public byte[] SymmetricProofTokenBytes
		{
			get
			{
				if (false == _symmetricProofTokenComputed)
				{
					lock (_symmetricProofTokenSyncRoot)
					{
						if (false == _symmetricProofTokenComputed)
						{
							#region Server provided the key --> User it!
							if (null != ProtectedKeyBytes)
							{
								_symmetricProofTokenBytes = (byte[])ProtectedKeyBytes.Clone();
							}
							#endregion

							#region Server provided entropy and/ or computation algorithm --> Combine with client entropy
							else if (!String.IsNullOrEmpty(ComputedKeyAlgorithm) || EntropyBytes != null)
							{
								if (null == EntropyBytes)
								{
									throw new InvalidOperationException("Server failed to provide entropy together with key computation algorithm.");
								}

								if (String.IsNullOrEmpty(ComputedKeyAlgorithm))
								{
									throw new InvalidOperationException("Server failed to provide key computation algorithm alongside entropy.");
								}

								if (!String.Equals(ComputedKeyAlgorithm, WSTrust13Constants.ComputedKeyAlgorithms.PSHA1))
								{
									throw new InvalidOperationException(
										String.Format("Unknown key computation algorithm: '{0}'. Expected: '{1}'.",
											ComputedKeyAlgorithm,
											WSTrust13Constants.ComputedKeyAlgorithms.PSHA1));
								}

								if (null == RST)
								{
									throw new InvalidOperationException("RST is required to compute the symmetric proof token.");
								}

								if (null == RST.EntropyBytes)
								{
									throw new InvalidOperationException("RST must contain entropy when server returns a key computation algorithm.");
								}

								int keySize = RequestSecurityToken.SymmetricKeySizeInBitsConstant;
								if (null != KeySizeInBits)
								{
									keySize = KeySizeInBits.Value;
								}
								else if (null != RST.KeySizeInBits)
								{
									keySize = RST.KeySizeInBits.Value;
								}

								_symmetricProofTokenBytes = CalculatePSHA1(RST.EntropyBytes, EntropyBytes, keySize);
							}
							#endregion

							#region Client provided entropy
							else if (null != RST && null != RST.EntropyBytes)
							{
								_symmetricProofTokenBytes = RST.EntropyBytes;
							}
							#endregion

							#region No proof key
							else
							{
								_symmetricProofTokenBytes = null;
							}
							#endregion

							_symmetricProofTokenComputed = true;
						}
					}
				}

				return _symmetricProofTokenBytes;
			}
			set { _symmetricProofTokenBytes = value; _symmetricProofTokenComputed = true; }
		}

		/// <summary>
		/// Algorithm that should be used to combina client/ server entropy
		/// </summary>
		public string ComputedKeyAlgorithm
		{
			get { return _computedKeyAlgorithm; }
			set { _computedKeyAlgorithm = value; }
		}

		/// <summary>
		/// Response created time
		/// </summary>
		public DateTime Created
		{
			get { return _created; }
			set { _created = value; }
		}

		/// <summary>
		/// Entropy returned by the server
		/// </summary>
		public byte[] EntropyBytes
		{
			get { return _entropyBytes; }
			set { _entropyBytes = value; }
		}

		/// <summary>
		/// Response expires time
		/// </summary>
		public DateTime Expires
		{
			get { return _expires; }
			set { _expires = value; }
		}

		/// <summary>
		/// KeySize (in bits) for the proof token
		/// </summary>
		public int? KeySizeInBits
		{
			get { return _keySizeInBits; }
			set { _keySizeInBits = value; }
		}

		/// <summary>
		/// Requested proof key type
		/// </summary>
		public string KeyType
		{
			get { return _keyType; }
			set { _keyType = value; }
		}

		public XNode ProofTokenReference
		{
			get { return _proofTokenReference; }
			set { _proofTokenReference = value; }
		}

		public byte[] ProtectedKeyBytes
		{
			get { return _protectedKeyBytes; }
			set { _protectedKeyBytes = value; }
		}

		/// <summary>
		/// The RST used to retrieve this RSTR
		/// </summary>
		internal RequestSecurityToken RST
		{
			get { return _rst; }
			set { _rst = value; }
		}

		/// <summary>
		/// Calculate key calculated key entropy using PHSA1
		/// </summary>
		/// <param name="client"></param>
		/// <param name="serverserverEntropy"></param>
		/// <param name="sizeBits"></param>
		/// <returns></returns>
		public static byte[] CalculatePSHA1(byte[] client, byte[] server, int sizeBits)
		{
			int sizeBytes = sizeBits / 8;

#if SILVERLIGHT
			using (HMAC hash = new HMACSHA1())
			{
				hash.Key = client;
				int bufferSize = hash.HashSize / 8 + server.Length;
				int i = 0;

				byte[] b1 = server;
				byte[] b2 = new byte[bufferSize];
				byte[] temp = null;
				byte[] psha = new byte[sizeBytes];


				while (i < sizeBytes)
				{
					hash.Initialize();
					b1 = hash.ComputeHash(b1);
					b1.CopyTo(b2, 0);
					server.CopyTo(b2, hash.HashSize / 8);

					temp = hash.ComputeHash(b2);

					for (int j = 0; j < temp.Length; j++)
					{
						if (i < sizeBytes)
						{
							psha[i] = temp[j];
							i++;
						}
						else
						{
							break;
						}
					}
				}
				return psha;
			}
#else
            // TODO: Verify performance. There is no necessity to convert to/from IBuffer in intermediate stage; code below does it to keep compatibility with SILVERLIGHT code.

            MacAlgorithmProvider hash = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha1);
            CryptographicKey key = hash.CreateKey(client.AsBuffer());
            int bufferSize = Convert.ToInt32(hash.MacLength) + server.Length;
            int i = 0;

            byte[] b1 = server;
            byte[] b2 = new byte[bufferSize];
            byte[] temp = null;
            byte[] psha = new byte[sizeBytes];

            while (i < sizeBytes)
            {
                b1 = CryptographicEngine.Sign(key, b1.AsBuffer()).ToArray();
                b1.CopyTo(b2, 0);
                server.CopyTo(b2, Convert.ToInt32(hash.MacLength));

                temp = CryptographicEngine.Sign(key, b2.AsBuffer()).ToArray();

                for (int j = 0; j < temp.Length; j++)
                {
                    if (i < sizeBytes)
                    {
                        psha[i] = temp[j];
                        i++;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            return psha;
#endif
		}


	}
}
